/*
 * RMatrix.h
 *
 *  Created on: Jul 13, 2011
 *      Author: burek
 */

#ifndef RMATRIX_H_
#define RMATRIX_H_

#include <cstdlib>
#include "../util.h"

template <typename T>
class RMatrix
{
protected:
	T * data;
	int width, height;

	void reallocate()
	{
		if(data != NULL)
			free(data);

		data = (T*)malloc(width * height * sizeof(T));
	}

public:
	T dummy;

	RMatrix(int w, int h)
	{
		width = w;
		height = h;
		data = NULL;
		reallocate();
	}

	RMatrix()
	{
		width = 0;
		height = 0;
		data = NULL;
	}

	RMatrix * generateMip()
	{
		RMatrix * mip = new RMatrix(width / 2, height / 2);
		for(int y = 0; y < height / 2; ++y)
		{
			for(int x = 0; x < width / 2; ++x)
			{
				mip->set(x, y,
						(cget(x*2, y*2) + cget(x*2+1, y*2) +
								cget(x*2,y*2+1) + cget(x*2+1, y*2+1)) / 4);
			}
		}
		return mip;
	}

	RMatrix * generateMaxMip()
	{
		RMatrix * mip = new RMatrix(width / 2, height / 2);
		for(int y = 0; y < height / 2; ++y)
		{
			for(int x = 0; x < width / 2; ++x)
			{
				mip->set(x, y,
						rMax(rMax(cget(x*2, y*2), cget(x*2+1, y*2)),
								rMax(cget(x*2,y*2+1), cget(x*2+1, y*2+1))));
			}
		}
		return mip;
	}

	RMatrix * generateMinMip()
	{
		RMatrix * mip = new RMatrix(width / 2, height / 2);
		for(int y = 0; y < height / 2; ++y)
		{
			for(int x = 0; x < width / 2; ++x)
			{
				mip->set(x, y,
						rMax(rMax(cget(x*2, y*2), cget(x*2+1, y*2)),
								rMax(cget(x*2,y*2+1), cget(x*2+1, y*2+1))));
			}
		}
		return mip;
	}

	void setDummy(T d)
	{
		dummy = d;
	}

	void fillRect(int X, int Y, int W, int H, const T & val)
	{
		if(data == NULL) return;
		if(X < 0) X = 0;
		if(Y < 0) Y = 0;
		if(X >= width) X = width - 1;
		if(Y >= height) Y = height - 1;

		for(int y = Y; y < Y + H; ++y)
		{
			int start = y * height + X;
			int end = start + W;
			for(int x = start; x < end; ++x)
			{
				data[x] = val;
			}
		}
	}

	void clear(const T & val)
	{
		int len = width * height;
		for(int i = 0; i < len; ++i)
			data[i] = val;
	}

	T & get(int x, int y)
	{
		if(data == NULL) return dummy;
		if(x < 0 || y < 0 || x >= width || y >= height) return dummy;

		return data[y * width + x];
	}

	T & cget(int x, int y)
	{
		if(data == NULL) return dummy;
		if(x < 0) x = 0;
		if(y < 0) y = 0;
		if(x >= width) x = width - 1;
		if(y >= height) y = height - 1;

		return data[y * width + x];
	}

	T & operator()(int x, int y)
	{
		if(data == NULL) return dummy;
		if(x < 0 || y < 0 || x >= width || y >= height) return dummy;

		return data[y * width + x];
	}

	void set(int x, int y, const T & val)
	{
		if(data == NULL) return;
		if(x < 0 || y < 0 || x >= width || y >= height) return;
		data[y * width + x] = val;
	}

	void rollColumns(int offset)
	{
		if((offset % width) == 0) return;
		T * auxColumn = (T*)malloc(height * sizeof(T));

		if(offset > 0)
		{
			for(int y = 0; y < height; ++y)
				auxColumn[y] = data[y * width];

			for(int x = 0; x < width; ++x)
			{
				int X = (x + offset) % width;
				if(X < 0) X += width;

				if(X == 0)
				{
					for(int y = 0; y < height; ++y)
					{
						data[y * width + x] = auxColumn[y];
					}
				}
				else
				{
					for(int y = 0; y < height; ++y)
					{
						data[y * width + x] = data[y * width + X];
					}
				}
			}
		}
		else
		{
			for(int y = 0; y < height; ++y)
				auxColumn[y] = data[y * width + width - 1];

			for(int x = width - 1; x >= 0; --x)
			{
				int X = (x + offset) % width;
				if(X < 0) X += width;

				if(X == width - 1)
				{
					for(int y = 0; y < height; ++y)
					{
						data[y * width + x] = auxColumn[y];
					}
				}
				else
				{
					for(int y = 0; y < height; ++y)
					{
						data[y * width + x] = data[y * width + X];
					}
				}
			}
		}

		free(auxColumn);
	}

	void rollRows(int offset)
	{
		if((offset % height) == 0) return;
		T * auxRow = (T*)malloc(width * sizeof(T));

		if(offset > 0)
		{
			for(int x = 0; x < width; ++x)
				auxRow[x] = data[x];

			for(int y = 0; y < height; ++y)
			{
				int Y = (y + offset) % height;
				if(Y < 0) Y += height;

				if(Y == 0)
				{
					for(int x = 0; x < width; ++x)
					{
						data[y * width + x] = auxRow[x];
					}
				}
				else
				{
					for(int x = 0; x < width; ++x)
					{
						data[y * width + x] = data[Y * width + x];
					}
				}
			}
		}
		else
		{
			for(int x = 0; x < width; ++x)
				auxRow[x] = data[(height - 1) * height + x];

			for(int y = height - 1; y >= 0; --y)
			{
				int Y = (y + offset) % height;
				if(Y < 0) Y += height;

				if(Y == height - 1)
				{
					for(int x = 0; x < width; ++x)
					{
						data[y * width + x] = auxRow[x];
					}
				}
				else
				{
					for(int x = 0; x < width; ++x)
					{
						data[y * width + x] = data[Y * width + x];
					}
				}
			}

		}

		free(auxRow);
	}

	void reallocate(int w, int h)
	{
		if(w > 0 && h > 0)
		{
			width = w;
			height = h;
			reallocate();
		}
	}

	int getWidth() const
	{
		return width;
	}

	int getHeight() const
	{
		return height;
	}

	T bilSample(float u, float v)
	{
		int indX1 = (int)u;
		int indY1 = (int)v;
		int indX2 = indX1 + 1;
		int indY2 = indY1 + 1;
		clamp(indX1, 0, width - 1);
		clamp(indY1, 0, height - 1);
		clamp(indX2, 0, width - 1);
		clamp(indY2, 0, height - 1);
		float cu = u - indX1;
		float cv = v - indY1;
		if(cu < 0) cu = 0.0f;
		if(cv < 0) cv = 0.0f;
		T a, b;
		T s1 = get(indX1, indY1);
		T s2 = get(indX2, indY1);
		T s3 = get(indX1, indY2);
		T s4 = get(indX2, indY2);
		a = (s2 - s1) * cu + s1;
		b = (s4 - s3) * cu + s3;
		return (b - a) * cv + a;

	}

	void bltTo(RMatrix<T> & m, int srcX, int srcY, int W, int H, int dstX, int dstY)
	{
		int sx = srcX, sy = srcY, dx = dstX, dy = dstY;
		for(int y = 0; y < H; ++y)
		{
			for(int x = 0; x < W; ++x)
			{
				m.set(dx, dy, get(sx, sy));
				sx++;
				dx++;
			}
			sy++;
			dy++;
			sx -= W;
			dx -= W;
		}
	}

	T * operator[](int i)
	{
		return &(data[i * height]);
	}

	~RMatrix()
	{
		if(data != NULL)
			free(data);
	}
};

#endif /* RMATRIX_H_ */
